package com.thtfpc.discovery;

import com.thtfpc.ssdp.*;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;

public class ScreenScanner extends SSDPChannel implements SimpleServiceFinder.SearchCallback {

    public static class RenderResult {
        public int errorCode;
        public String sessionId;
        public String hostAddress;
        public int port;

        @Override
        public String toString() {
            return "RenderResult{" +
                    "errorCode=" + errorCode +
                    ", sessionId='" + sessionId + '\'' +
                    ", hostAddress='" + hostAddress + '\'' +
                    ", port=" + port +
                    '}';
        }
    }

    private static class PendingSession {
        public PendingSession(String sessionId, long timestamp) {
            this.sessionId = sessionId;
            this.timestamp = timestamp;
        }

        public String sessionId;
        public long timestamp;
    }

    public interface ScannerCallback {
        void onDeviceOnlined(SSDPDevice device);

        void onDeviceOfflined(SSDPDevice device);

        void onRenderResult(RenderResult result);
    }


    protected Map<String, PendingSession> pendingSessionMap;
    protected SimpleServiceFinder simpleServiceScanner;
    protected String rtspURL;
    protected WeakReference<ScannerCallback> renderCallbackWeakReference;

    public ScreenScanner(String rtspURL, ScannerCallback renderCallback) {
        super(19034, false);
        this.rtspURL = rtspURL;
        pendingSessionMap = new HashMap<>();
        renderCallbackWeakReference = new WeakReference<>(renderCallback);
        simpleServiceScanner = new SimpleServiceFinder("urn:schemas-upnp-org:device:MediaRenderer:1", this, 1920);
    }

    public void startSearch() {
        if (isRunning() == false) {
            return;
        }
        try {
            simpleServiceScanner.start();
        }
        catch (IOException ex) {
        }
    }

    public void stopSearch() {
        try {
            this.simpleServiceScanner.stop();
        }
        catch (IOException ex) {
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (this.simpleServiceScanner.isRunning()) {
            try {
                this.simpleServiceScanner.stop();
            }
            catch (IOException ex) {
            }
        }
    }

    @Override
    protected void onMessage(SSDPMessage ssdpMessage) {
        /*
        HTTP/1.1 200 OK
        ACTION: SCREEN_RENDER
        RESULT: 0
        SESSION: xxxxx-xxxx-xxxx-xxxx
        USN: xxx-xxxx-xxx-xxxx
         */
        String messageType = ssdpMessage.getMessageType();
        String action = ssdpMessage.getHeader("ACTION");
        int resultCode = 99;
        try {
            resultCode = Integer.parseInt(ssdpMessage.getHeader("RESULT"));
        }
        catch (NumberFormatException ex) {
        }
        String sessionId = ssdpMessage.getHeader("SESSION");
        String usn = ssdpMessage.getHeader("USN");

        if ("HTTP/1.1 200 OK".equals(messageType)
                && "SCREEN_RENDER".equals(action)
                && sessionId != null
                && usn != null) {
            RenderResult result = new RenderResult();
            result.errorCode = resultCode;
            result.sessionId = sessionId;
            result.hostAddress = ssdpMessage.getAddress();
            result.port = ssdpMessage.getPort();
            onRenderResult(result);
        }
    }


    /*
    发起render链接请求,ssdpdevice中，serverURL描述了被发布的服务的地址，要先和serverURL进行握手
     */
    public void requestRender(SSDPDevice device) {
        /*
        SCR-RENDER * HTTP/1.1
        ACTION: SCREEN_RENDER_REQ
        RT: urn:schemas-upnp-org:device:MediaRenderer:1
        LOCATION: rtsp://xxxx/xxx
        SESSION: xxx-xxx-xxx-xxx
         */
        if (isRunning() == false) {
            return;
        }
        String[] urlps = null;
        if (device.getServiceUrl() == null || ((urlps = device.getServiceUrl().split(":")).length != 2)) {
            return;
        }
        PendingSession pendingRequest = new PendingSession(UUID.randomUUID().toString(), System.currentTimeMillis());
        synchronized (pendingSessionMap) {
            pendingSessionMap.clear();
            pendingSessionMap.put(pendingRequest.sessionId, pendingRequest);
        }

        SSDPMessage message = new SSDPMessageBuilder()
                .messageType("SCR-RENDER * HTTP/1.1")
                .header("ACTION", "SCREEN_RENDER")
                .header("RT", "urn:schemas-upnp-org:device:MediaRenderer:1")
                .header("USN", device.getId())
                .header("LOCATION", this.rtspURL)
                .header("SESSION", pendingRequest.sessionId)
                .address(urlps[0])
                .port(Integer.parseInt(urlps[1]))
                .build();
        sendMessage(message);
    }


    protected void updateExpiredSessions() {
        long timestampNow = System.currentTimeMillis();
        Iterator<Map.Entry<String, PendingSession>> iterator = pendingSessionMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, PendingSession> entry = iterator.next();
            if ((timestampNow - entry.getValue().timestamp) > 6000) {
                iterator.remove();
            }
        }
    }

    protected void onRenderResult(RenderResult renderResult) {
        synchronized (pendingSessionMap) {
            updateExpiredSessions();
            if (pendingSessionMap.containsKey(renderResult.sessionId)) {
                pendingSessionMap.remove(renderResult.sessionId);
                ScannerCallback callback = renderCallbackWeakReference.get();
                if (callback != null) {
                    callback.onRenderResult(renderResult);
                }
            }
        }
    }

    @Override
    public void onServiceOnlined(SSDPDevice device) {
        ScannerCallback callback = renderCallbackWeakReference.get();
        if (callback != null) {
            callback.onDeviceOnlined(device);
        }
    }

    @Override
    public void onServiceOfflined(SSDPDevice device) {
        ScannerCallback callback = renderCallbackWeakReference.get();
        if (callback != null) {
            callback.onDeviceOfflined(device);
        }
    }
}

